// Copyright 2011 Andreas Louca. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package gosnmp

/*
#include <net-snmp/net-snmp-config.h>
#include <net-snmp/net-snmp-includes.h>

static char* value_to_str(const netsnmp_variable_list *vars) {
	char *buf = (char*)malloc(256*sizeof(char));
	
	snprint_value (buf, 256, vars->name, vars->name_length, vars);
	
	return buf;
}

static char* oid_to_str(oid *name, size_t name_length) {
	int i = 0;
	char *resp = (char*)malloc(sizeof(char) * name_length+1);
	resp[name_length] = '\0';
	for (i = 0; i < name_length; i++) {
		sprintf(resp, "%s.%lu", resp, name[i]);
	}

	return resp;
}



*/
import "C"

import (
	"fmt"
	"os"
	"unsafe"
	"strconv"
)

type SnmpTarget struct {
	session  C.netsnmp_session
	wsession *C.netsnmp_session
}

type Pdu struct {
	Oid     string
	Value   string
	OidType	string
}

func NewSNMPTarget(target string, community string) (snmp SnmpTarget, err os.Error) {
	fmt.Printf("Got new target %s (%s)\n", target, community)

	// Initialize the SNMP library
	C.init_snmp(C.CString("gosnmp"))

	// Initialize the SNMP Session
	C.snmp_sess_init(&snmp.session)
	snmp.session.peername = C.CString(target)

	// Set the SNMP version number
	// *TODO*: Accept this as a parameter, but at the moment there is no point
	snmp.session.version = C.SNMP_VERSION_2c

	// set the SNMPv2c community name used for authentication
	c_community := C.CString(community)
	snmp.session.community = (*C.u_char)(unsafe.Pointer(c_community))
	snmp.session.community_len = C.strlen(c_community)

	// Open the session, will need to use this for future calls
	snmp.wsession = C.snmp_open(&snmp.session)

	if snmp.wsession == nil {
		C.snmp_perror(C.CString("ack"))
		err = os.NewError("Failed to open the SNMP session")
	}

	return
}

func (x *SnmpTarget) parseResponse(response *C.netsnmp_pdu) (responses []Pdu) {
	// Loop through the respose, and populate a slice of PDU Responses
	for vars := response.variables; vars != nil; vars = (*C.netsnmp_variable_list)(vars.next_variable) {

		C.print_variable(vars.name, vars.name_length, vars)
		var resp Pdu
		
		// Convert values to strings
		oid := C.oid_to_str(vars.name, vars.name_length)
		value := C.value_to_str(vars)

		resp.Oid = C.GoString(oid)
		resp.Value = C.GoString(value)
		responses = append(responses, resp)

		C.free(unsafe.Pointer(oid))
		C.free(unsafe.Pointer(value))
	}

	return
}

func (x *SnmpTarget) Set(pdus []Pdu) (responses []Pdu, err os.Error) {
	var pdu *C.netsnmp_pdu
	var response *C.netsnmp_pdu

	pdu = C.snmp_pdu_create(C.SNMP_MSG_SET)

	var failures int = 0

	for _, v := range pdus {
		var name [128]C.oid
		var nameLength C.size_t = 128
		
		type_value, _ := strconv.Atoi(v.OidType)
		cOid := C.CString(v.Oid)
		cValue := C.CString(v.Value)

		if C.snmp_parse_oid(cOid, &name[0], &nameLength) == nil {
			C.snmp_perror(cOid)
			failures++
		} else if C.snmp_add_var(pdu, &name[0], nameLength, (C.char)(type_value), cValue) != 0 {
			C.snmp_perror(cOid)
			failures++
		}

		C.free(unsafe.Pointer(cOid))
		C.free(unsafe.Pointer(cValue))
	}

	if failures > 0 {
		err = os.NewError("Unable to parse request list. Consult console output")
		return
	}

	status := C.snmp_synch_response(x.wsession, pdu, &response)

	if status == C.STAT_TIMEOUT {
		err = os.NewError("Request timeout")
		return
	}

	return x.parseResponse(response), nil
}

func (x *SnmpTarget) Get(pdus []Pdu) (responses []Pdu, err os.Error) {
	var pdu *C.netsnmp_pdu
	var response *C.netsnmp_pdu

	pdu = C.snmp_pdu_create(C.SNMP_MSG_GET)

	// Add all OIDs in to the Request
	for _, v := range pdus {
		var anOID [128]C.oid
		var oid_len int = 128
		
		C.read_objid(C.CString(v.Oid), &anOID[0], (*C.size_t)(&oid_len))
		C.snmp_add_null_var(pdu, &anOID[0], oid_len)
		fmt.Printf("Oid %s Len: %d\n", v.Oid, oid_len)
	}

	// Send request out
	status := C.snmp_synch_response(x.wsession, pdu, &response)

	defer func() {
		if response != nil {
			C.snmp_free_pdu(response)
		}
	}()

	if status == C.STAT_SUCCESS && response.errstat == C.SNMP_ERR_NOERROR {
		responses = x.parseResponse(response)
	} else {
		if status == C.STAT_SUCCESS {
			err = os.NewError("Error in packet: " + C.GoString(C.snmp_errstring((C.int)(response.errstat))))
		} else {
			perr := C.CString("snmpget")
			C.snmp_sess_perror(perr, x.wsession)
			C.free(unsafe.Pointer(perr))
		}
	}

	return
}

func (x *SnmpTarget) Close() {
	C.snmp_close(x.wsession)
}
